package net.melon.sysres.inst;

import java.io.File;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import net.esj.basic.core.engine.factory.bean.BeanFactory;
import net.esj.basic.core.engine.schedule.ScheduleFactory;
import net.esj.basic.core.engine.schedule.ScheduleType;
import net.esj.basic.core.engine.schedule.Scheduler;
import net.esj.basic.dao.DaoProxy;
import net.esj.basic.dao.jdbc.JdbcDao;
import net.esj.basic.dao.querybuilder.QueryBuilder;
import net.esj.basic.pojo.system.ApplicationClass;
import net.esj.basic.service.impl.DefaultServiceImpl;
import net.esj.basic.service.provider.ApplicationProvider;
import net.esj.basic.service.provider.UserRuleProvider;
import net.esj.basic.utils.DateFormat;
import net.esj.basic.utils.DateUtils;
import net.esj.basic.utils.StringUtils;
import net.esj.basic.utils.UUIDCreator;
import net.melon.jdbc.core.RowMapper;
import net.melon.sysres.Constants;
import net.melon.sysres.SysresUtils;
import net.melon.sysres.manager.SystemManager;
import net.melon.sysres.pojo.Attachment;
import net.melon.sysres.pojo.ExceptionCode;
import net.melon.sysres.pojo.I18N;
import net.melon.sysres.pojo.SysApplication;
import net.melon.sysres.pojo.SysFieldProperty;
import net.melon.sysres.pojo.SysProperty;
import net.melon.sysres.pojo.SysScheduler;
import net.melon.sysres.pojo.SysUserRule;

import org.apache.commons.io.FileUtils;

@Singleton
@Named("systemManager")
public class SystemManagerImpl extends DefaultServiceImpl implements SystemManager {

	private static Map<String, SysProperty> propertyCache = new HashMap<String, SysProperty>();
	private static Map<String, SysScheduler> schedulerCache = new HashMap<String, SysScheduler>();
	
	private static Map<String,I18N> i18nCache = new HashMap<String,I18N>();
	private static Map<String,ExceptionCode> exceptionCodeCache = new HashMap<String,ExceptionCode>();
	
	private BeanFactory beanFactory;
	
	@Override
	public void deleteProperty(SysProperty property) {
		delete(property);
		reloadProperties();
	}

	
	@Override
	public List<SysProperty> loadProperties(boolean force) {
		if(propertyCache.isEmpty() || force){
			reloadProperties();
		}
		return new ArrayList<SysProperty>(propertyCache.values());
	}

	
	@Override
	public SysProperty saveProperty(SysProperty property) {
		save(property);
		reloadProperties();
		return property;
	}

	@Override
	public SysProperty updateProperty(SysProperty property) {
		update(property);
		reloadProperties();
		return property;
	}

	
	private void reloadProperties(){
		propertyCache.clear();
		List<SysProperty> list =findAll(SysProperty.class);
		for(SysProperty p : list){
			propertyCache.put(p.getId(), p);
		}
	}
	

	@Inject
	public void setBeanFactory(BeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}

	@Override
	public void mkDir(File parent, String name) {
		File f = new File(parent, name);
		if(!f.exists()){
			f.mkdirs();
		}
	}

	@Override
	public void uploadFile(File parent, File realfile) {
		
	}

	@Override
	public File[] loadChildFiles(File parent) {
		return parent.listFiles();
	}

	@Override
	public void deleteUserRule(SysUserRule userRule) {
		delete(userRule);
		resetUserRules();
	}

	@Override
	public SysUserRule saveUserRule(SysUserRule userRule) {
		save(userRule);
		resetUserRules();
		return userRule;
	}

	@Override
	public SysUserRule updateUserRule(SysUserRule userRule) {
		update(userRule);
		resetUserRules();
		return userRule;
	}
	
	private void resetUserRules(){
		List list =findAll(SysUserRule.class);
		UserRuleProvider.resetUserRules(list);
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		super.afterPropertiesSet();
		resetUserRules();
	}

	@Override
	public Attachment saveAttachment(File templateFile,String templateFileName) throws IOException {
		Attachment attach = new Attachment();
		Date now = DateUtils.now();
		String uuid = UUIDCreator.create();
		String path = getFullPath(now);
		File dir = new File(path);
		if(!dir.exists()){
			dir.mkdirs();
		}
		File file = new File(path+uuid+Constants.MELON_SYSRES_ATTACHMENT_POSTFIX);
		FileUtils.copyFile(templateFile, file);
		String url = path.substring(SysresUtils.getBasicUploadPath().length());
		attach.setUrl(url);
		attach.setId(uuid);
		attach.setAttachName(templateFileName);
		attach.setGmtCreate(now);
		save(attach);
		return attach;
	}

	@Override
	public Attachment getAttachment(String id) {
		Attachment attach = get(id, Attachment.class);
		return attach;
	}

	@Override
	public Attachment updateAttachment(String id,File templateFile,String templateFileName) throws IOException {
		Attachment attach = get(id, Attachment.class);
		if(attach==null){
			return attach;
		}
		File oldFile = new File(SysresUtils.getBasicUploadPath()+attach.getUrl());
		oldFile.deleteOnExit();
		
		Date now = DateUtils.now();
		String path = getFullPath(now);
		File dir = new File(path);
		if(!dir.exists()){
			dir.mkdirs();
		}
		
		File file = new File(path+attach.getId()+Constants.MELON_SYSRES_ATTACHMENT_POSTFIX);
		FileUtils.copyFile(templateFile, file);
		String url = path.substring(SysresUtils.getBasicUploadPath().length());
		
		attach.setUrl(url);
		attach.setAttachName(templateFileName);
		update(url);
		return attach;
	}

	@Override
	public void deleteAttachment(String id) {
		Attachment attach =get(id, Attachment.class);
		if(attach==null){
			return;
		}
		File oldFile = new File(SysresUtils.getBasicUploadPath()+attach.getUrl());
		oldFile.deleteOnExit();
		delete(attach);
	}
	
	@Override
	public I18N getI18N(String sourceName, String code, Locale locale) {
		String k = sourceName+"."+code+"."+locale.toString();
		I18N i18n = i18nCache.get(k);
		if(i18n==null){
			loadI18N();
			i18n = i18nCache.get(k);
		}
		return i18n;
	}

	@Override
	public ExceptionCode getExceptionCode(String code, Locale locale) {
		String k = code+"."+locale.toString();
		ExceptionCode ec = exceptionCodeCache.get(k);
		if(ec==null){
			loadExceptionCode();
			ec = exceptionCodeCache.get(k);
		}
		return ec;
	}
	
	@Override
	public void reloadI18N() {
		loadI18N();
	}

	@Override
	public void reloadExceptionCode() {
		loadExceptionCode();
	}
	
	private void loadI18N(){
		List<I18N> list = findAll(I18N.class);
		i18nCache.clear();
		for(I18N i18n: list){
			String k = i18n.getClazz()+"."+i18n.getCode()+"."+i18n.getLang();
			i18nCache.put(k, i18n);
		}
	}
	
	private void loadExceptionCode(){
		List<ExceptionCode> list = findAll(ExceptionCode.class);
		exceptionCodeCache.clear();
		for(ExceptionCode ec: list){
			String k = ec.getCode()+"."+ec.getLang();
			exceptionCodeCache.put(k, ec);
		}
	}
	
	private static final java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat(
			"yyyyMMdd");
	
	protected String getFullPath(Date date){
		StringBuilder sb = new StringBuilder();
		sb.append(SysresUtils.getBasicUploadPath());
		String str = formatter.format(date);
		sb.append(str.substring(0, 4)).append(File.separator).append(str.substring(4, 6)).append(File.separator).append(str.substring(6)).append(File.separator);
		return sb.toString();
	}

	@Override
	public List<SysFieldProperty> listFieldProperty(String model,String url,Map params){
		QueryBuilder<SysFieldProperty> qb = createQueryBuilder(SysFieldProperty.class);
		qb.eq("model", model).OrderByAsc("id");
		if(!StringUtils.hasText(url)){
			//qb.isNull("url");
			return queryListByQueryBuilder(qb);
		}else{
			qb.like("url", url+"%");
			List<SysFieldProperty> list = queryListByQueryBuilder(qb);
			List<SysFieldProperty> retal = new ArrayList<SysFieldProperty>();
			for(SysFieldProperty fp : list){
				if(checkParams(fp.params(), params)){
					retal.add(fp);
				}
			}
			return retal;
		}
	}

	@Override
	public SysFieldProperty updateFieldProperty(SysFieldProperty property){
		SysFieldProperty exists  = get(property.getId(), SysFieldProperty.class);
		if(property==null){
			return exists;
		}
		if(exists.canAlterAdd()){
			exists.setAdd(property.isAdd());
		}
		if(exists.canAlterEdit()){
			exists.setEdit(property.isEdit());
		}
		if(exists.canAlterList()){
			exists.setList(property.isList());
		}
		if(exists.canAlterSort()){
			exists.setSort(property.isSort());
		}
		if(exists.canAlterView()){
			exists.setView(property.isView());
		}
		exists.setDefaults(property.getDefaults());
		update(exists);
		return exists;
	}
	

	@Override
	public List<SysApplication> applications() {
		return findAll(SysApplication.class, true);
	}

	@Override
	public SysApplication getApplication(String id) {
		return get(id, SysApplication.class);
	}

	@Override
	public SysApplication saveApplication(SysApplication entity) {
		save(entity);
		return entity;
	}

	@Override
	public SysApplication updateApplication(SysApplication entity) {
		update(entity);
		return entity;
	}

	
	private static final String UPDATE_SCHEDULER_STATE = "UPDATE SYS_SCHEDULER SET IS_START = ( CASE WHEN AUTO_START = 1 THEN 1 ELSE 0 END ) ";
	private static final String FIND_ALL_AUTO_START_SCHEDULER = "SELECT ID FROM SYS_SCHEDULER WHERE AUTO_START = 1 ";
	private List<SysScheduler> getAllAutoStartSysScheduler(){
		DaoProxy.getProxy(JdbcDao.class).getJdbcTemplate().update(UPDATE_SCHEDULER_STATE);
		List<SysScheduler> list = DaoProxy.getProxy(JdbcDao.class).getJdbcTemplate().query(FIND_ALL_AUTO_START_SCHEDULER, new RowMapper<SysScheduler>() {
			@Override
			public SysScheduler mapRow(ResultSet rs, int arg1) throws SQLException {
				SysScheduler s = new SysScheduler();
				s.setId(rs.getString("ID"));
				return s; 
			}
		});
		if(list!=null){
			return list;
		}else{
			return new ArrayList<SysScheduler>();
		}
	}
	
	@Override
	public void reloadApplicationClasses(){
		List<SysFieldProperty> all = findAll(SysFieldProperty.class);
		
		for(SysFieldProperty property : all){
			String className = property.getPropertyId().substring(0, property.getPropertyId().lastIndexOf("."));
			String fieldName = property.getPropertyId().substring(property.getPropertyId().lastIndexOf(".")+1);
			Class<?> clazz=null;
			try {
				clazz = Class.forName(className);
			} catch (ClassNotFoundException e) {
			}
			if(clazz==null){
				continue;
			}
			ApplicationClass apclass = ApplicationProvider.getClass(clazz);
			if(apclass!=null){
				apclass.addAttribute(fieldName,property.getUrl(), property.isAdd(), property.isEdit(), property.isList(), property.isView(), property.isSort(), property.getDefaults());
			}else{
				apclass = new ApplicationClass(clazz);
				apclass.addAttribute(fieldName,property.getUrl(), property.isAdd(), property.isEdit(), property.isList(), property.isView(), property.isSort(), property.getDefaults());
			}
			ApplicationProvider.regisitry(clazz, apclass);
		}
	}
	
	@Override
	public void cloneFieldProperty(String model,String url){
		QueryBuilder<SysFieldProperty> qb = createQueryBuilder(SysFieldProperty.class);
		qb.eq("model", model).isNull("url").OrderByAsc("id");
		List<SysFieldProperty> list = queryListByQueryBuilder(qb);
		for(SysFieldProperty fp:list){
			SysFieldProperty n = new SysFieldProperty();
			n.setAdd(fp.isAdd());
			n.setDefaults(fp.getDefaults());
			n.setEdit(fp.isEdit());
			n.setEditable(fp.getEditable());
			n.setList(fp.isList());
			n.setModel(fp.getModel());
			n.setPropertyId(fp.getPropertyId());
			n.setSort(fp.isSort());
			n.setUrl(url);
			n.setView(fp.isView());
			save(n);
		}
	}
	
	@Override
	public List<String> listFieldPropertyUrl(String model){
		QueryBuilder<SysFieldProperty> qb = createQueryBuilder(SysFieldProperty.class);
		qb.eq("model", model).isNotNull("url").setDistinct("url");
		List l = queryListByQueryBuilder(qb);
		return l;
	}
	
	private final boolean checkParams(Map<String, String[]> params,Map requestParams){
		boolean pass = true;
		for(Object o :params.entrySet()){
			Map.Entry<String, String[]> entry = (Entry) o;
			Object v = requestParams.get(entry.getKey());
			if(v!=null && v.getClass().isArray()){
				v = ((Object[])v)[0];
			}
			if(v== null){
				return false;
			}
			boolean has = false;
			for(String str : entry.getValue()){
				if(v.equals(str)){
					has |= true;
				}
			}
			pass &= has;
		}
		return pass;
	}

}
